import type { Command, CommandContext, CommandOption, CommandArgument } from '../types/index.js';

export abstract class BaseCommand implements Command {
  abstract name: string;
  abstract description: string;
  aliases?: string[];
  options?: CommandOption[];
  arguments?: CommandArgument[];
  examples?: string[];

  abstract execute(context: CommandContext): Promise<void>;

  protected validateOptions(context: CommandContext): void {
    if (!this.options) return;

    for (const option of this.options) {
      const value = context.options[option.name];

      if (option.required && (value === undefined || value === null)) {
        throw new Error(`Required option --${option.name} is missing`);
      }

      if (value !== undefined && option.choices && !option.choices.includes(value)) {
        throw new Error(
          `Invalid value for --${option.name}. Expected one of: ${option.choices.join(', ')}`
        );
      }

      if (value !== undefined && option.type === 'number' && isNaN(Number(value))) {
        throw new Error(`Option --${option.name} must be a number`);
      }

      if (value !== undefined && option.type === 'boolean' && typeof value !== 'boolean') {
        throw new Error(`Option --${option.name} must be a boolean`);
      }
    }
  }

  protected validateArguments(context: CommandContext): void {
    if (!this.arguments) return;

    const requiredArgs = this.arguments.filter(arg => arg.required);
    const providedArgs = context.args.length;

    if (providedArgs < requiredArgs.length) {
      const missingArgs = requiredArgs.slice(providedArgs).map(arg => arg.name);
      throw new Error(`Missing required arguments: ${missingArgs.join(', ')}`);
    }

    const maxArgs = this.arguments.some(arg => arg.variadic) 
      ? Infinity 
      : this.arguments.length;

    if (providedArgs > maxArgs) {
      throw new Error(`Too many arguments provided. Expected at most ${maxArgs}`);
    }
  }

  protected getUsage(): string {
    let usage = `genwavejs ${this.name}`;

    if (this.arguments) {
      for (const arg of this.arguments) {
        if (arg.required) {
          usage += ` <${arg.name}>`;
        } else {
          usage += ` [${arg.name}]`;
        }
        if (arg.variadic) {
          usage += '...';
        }
      }
    }

    if (this.options && this.options.length > 0) {
      usage += ' [options]';
    }

    return usage;
  }

  protected getHelp(): string {
    let help = `${this.description}\n\n`;
    help += `Usage: ${this.getUsage()}\n`;

    if (this.arguments && this.arguments.length > 0) {
      help += '\nArguments:\n';
      for (const arg of this.arguments) {
        const required = arg.required ? ' (required)' : '';
        help += `  ${arg.name.padEnd(20)} ${arg.description}${required}\n`;
      }
    }

    if (this.options && this.options.length > 0) {
      help += '\nOptions:\n';
      for (const option of this.options) {
        const required = option.required ? ' (required)' : '';
        const defaultValue = option.default !== undefined ? ` (default: ${option.default})` : '';
        const choices = option.choices ? ` (choices: ${option.choices.join(', ')})` : '';
        help += `  --${option.name.padEnd(18)} ${option.description}${required}${defaultValue}${choices}\n`;
      }
    }

    if (this.examples && this.examples.length > 0) {
      help += '\nExamples:\n';
      for (const example of this.examples) {
        help += `  ${example}\n`;
      }
    }

    if (this.aliases && this.aliases.length > 0) {
      help += `\nAliases: ${this.aliases.join(', ')}\n`;
    }

    return help;
  }
}

export class CommandRegistry {
  private commands: Map<string, Command> = new Map();
  private aliases: Map<string, string> = new Map();

  register(command: Command): void {
    this.commands.set(command.name, command);

    if (command.aliases) {
      for (const alias of command.aliases) {
        this.aliases.set(alias, command.name);
      }
    }
  }

  unregister(name: string): void {
    const command = this.commands.get(name);
    if (command) {
      this.commands.delete(name);
      
      if (command.aliases) {
        for (const alias of command.aliases) {
          this.aliases.delete(alias);
        }
      }
    }
  }

  get(name: string): Command | undefined {
    const commandName = this.aliases.get(name) || name;
    return this.commands.get(commandName);
  }

  has(name: string): boolean {
    const commandName = this.aliases.get(name) || name;
    return this.commands.has(commandName);
  }

  list(): Command[] {
    return Array.from(this.commands.values());
  }

  getNames(): string[] {
    return Array.from(this.commands.keys());
  }

  getAliases(): string[] {
    return Array.from(this.aliases.keys());
  }

  findSimilar(name: string): string[] {
    const allNames = [...this.getNames(), ...this.getAliases()];
    return allNames.filter(cmdName => 
      cmdName.includes(name) || name.includes(cmdName)
    ).slice(0, 3);
  }
}
